package com.zeratul.common.mybatis.core.sqlexec.plugin;

import cn.hutool.core.util.ObjectUtil;
import com.zeratul.common.mybatis.core.sqlexec.context.SqlContext;
import com.zeratul.common.mybatis.core.sqlexec.prop.LogFormat;
import com.zeratul.common.mybatis.core.sqlexec.prop.ZtExecSqlConfiguration;
import com.zeratul.common.mybatis.utils.MyBatisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * SQL语句记录插件、耗时
 */
@Intercepts({
        @Signature(
                type = StatementHandler.class,
                method = "query",
                args = {Statement.class, ResultHandler.class}
        ),
        @Signature(
                type = StatementHandler.class,
                method = "queryCursor",
                args = {Statement.class}
        ),
        @Signature(
                type = StatementHandler.class,
                method = "update",
                args = {Statement.class}
        )
})
@Slf4j
@RefreshScope
public class SQLExecPlugin implements Interceptor {

    @Autowired
    private ZtExecSqlConfiguration sqlConfiguration;

    /**
     * 插件拦截方法
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        //获取SQL日志输出标志
        Boolean sqlLog = SqlContext.getSqlLog();
        if (ObjectUtil.isNull(sqlLog) || !sqlLog) {
            return invocation.proceed();
        }

        //获取非代理的目标对象
        StatementHandler statementHandler = (StatementHandler) MyBatisUtils.getNoProxyTraget(invocation.getTarget());
//        System.out.println("Mybatis插件获取的当前对象：" + statementHandler);
        //获得当前执行的SQL语句
        MetaObject statmentObject = SystemMetaObject.forObject(statementHandler);
        //获取全局配置对象
        Configuration configuration = (Configuration) statmentObject.getValue("delegate.configuration");
        //获得需要编译的sql语句
        BoundSql boundSql = (BoundSql) statmentObject.getValue("delegate.boundSql");
        //获得sql
        String sql = boundSql.getSql();
        if (ObjectUtil.isEmpty(sql)) {
            return invocation.proceed();
        }
        //获得sql
        try {
            sql = sql.toLowerCase().trim().replace("\n", "")
                    .replaceAll("[ ]+", " ");
        } catch (Exception e) {
            sql = sql.toLowerCase().trim();
        }

        //输出Sql日志
        soutSql(sql, LogFormat.PREPARED);

        //获取所有的参数
        try {
            Map<Integer, Object> sqlParams = getSqlParams(boundSql, configuration);
            if (ObjectUtil.isNotEmpty(sqlParams)) {
                for (Map.Entry<Integer, Object> entry : sqlParams.entrySet()) {
                    //替换问号
                    sql = sql.replaceFirst("\\?", "'" + entry.getValue() + "'");
                }
            }
        } catch (Exception e) {
            log.warn("[SQL] params replace error - {}", e.getMessage());
        }

        //输出Sql日志
        soutSql(sql, LogFormat.EXECUTE);

        //执行目标方法
        //记录执行前的时间
        long beginTime = System.currentTimeMillis();
        Object result = invocation.proceed();
        long end = System.currentTimeMillis();
        //计算耗时
        double tTime = new BigDecimal(end - beginTime).divide(new BigDecimal(1000)).setScale(6, RoundingMode.DOWN).doubleValue();
        log.debug("[SQL] take up time - [" + tTime + "s]");

        return result;
    }



    /**
     * 获取SQL运行参数
     */
    private Map<Integer, Object> getSqlParams(BoundSql boundSql, Configuration configuration) {
        //参数集合
        Map<Integer, Object> params = new HashMap<>();

        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        Object parameterObject = boundSql.getParameterObject();
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }

                    //输出参数
                    soutParams(i, value, LogFormat.PREPARED);
                    params.put(i, value);
                }
            }
        }
        return params;
    }

    /**
     * 输出SQL格式
     * @return
     */
    private void soutSql(String sql, LogFormat logFormat){
        //格式类型
        LogFormat configFormat = sqlConfiguration.getLogFormat();

        //判断当前格式是否符合输出要求
        if (logFormat != configFormat) {
            return;
        }

        //SQL的最大长度
        Integer sqlMaxLength = sqlConfiguration.getSqlMaxLength();
        if (sqlMaxLength != -1 && sql.length() > sqlMaxLength) {
            sql = sql.substring(0, sqlMaxLength);
            sql += "...";
        }

        //记录当前的sql语句
        log.debug("[SQL] {} - {}", logFormat.getLogFormat(), sql);
    }

    /**
     * 输出参数日志
     */
    private void soutParams(Integer i, Object value, LogFormat logFormat) {
        //格式类型
        LogFormat configFormat = sqlConfiguration.getLogFormat();
        //判断当前格式是否符合输出要求
        if (configFormat != logFormat) {
            return;
        }

        //限制参数的最大数量
        Integer paramMaxLength = sqlConfiguration.getParamMaxLength();
        if (paramMaxLength != -1 && i >= paramMaxLength) {
            return;
        }

        log.debug("[SQL] params[{}] - {}", i, value);
    }
}
